package com.frank.oj.manager;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.frank.model.entity.judge.Judge;
import com.frank.model.entity.problem.Problem;
import com.frank.model.entity.user.Role;
import com.frank.model.entity.user.Session;
import com.frank.model.entity.user.UserAcproblem;
import com.frank.model.entity.user.UserInfo;
import com.frank.oj.common.exception.StatusFailException;
import com.frank.oj.common.exception.StatusSystemErrorException;
import com.frank.oj.constant.Constants;
import com.frank.oj.model.dto.ChangeEmailDTO;
import com.frank.oj.model.dto.ChangePasswordDTO;
import com.frank.oj.model.dto.CheckUsernameOrEmailDTO;
import com.frank.oj.model.vo.*;
import com.frank.oj.service.entity.problem.ProblemEntityService;
import com.frank.oj.service.entity.user.*;
import com.frank.oj.shiro.AccountProfile;
import com.frank.oj.utils.IpUtils;
import com.frank.oj.utils.RedisUtils;
import com.frank.oj.validator.CommonValidator;
import com.jthinking.common.util.ip.IPInfo;
import com.jthinking.common.util.ip.IPInfoUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author frank
 * @Date 2024/1/25
 */
@Component
public class AccountManager {
    @Resource
    private UserRoleEntityService userRoleEntityService;
    @Resource
    private UserInfoEntityService userInfoEntityService;
    @Resource
    private UserRecordEntityService userRecordEntityService;
    @Resource
    private RedisUtils redisUtils;
    @Resource
    private UserAcProblemEntityService userAcproblemEntityService;

    @Resource
    private ProblemEntityService problemEntityService;

    @Resource
    private SessionEntryService sessionEntityService;

    @Resource
    private CommonValidator commonValidator;

    @Resource
    private EmailManager emailManager;

    public UserAuthInfoVO getUserAuthInfo() {

        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        List<Role> roles = userRoleEntityService.getRolesByUid(userRole.getUid());
        UserAuthInfoVO userAuthInfoVO = new UserAuthInfoVO();
        userAuthInfoVO.setRoles(roles.stream().map(Role::getRole).collect(Collectors.toList()));
        return userAuthInfoVO;
    }

    public CheckUsernameOrEmailVO checkUsernameOrEmail(CheckUsernameOrEmailDTO checkUsernameOrEmailDTO) {

        String username = checkUsernameOrEmailDTO.getUsername();
        String email = checkUsernameOrEmailDTO.getEmail();

        boolean usernameExist = false;
        boolean emailExist = false;

        if (!StringUtils.isEmpty(email)) {
            email = email.trim();
            boolean isEmail = Validator.isEmail(email);
            if (isEmail) {
                LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(UserInfo::getEmail, email);
                UserInfo user = userInfoEntityService.getOne(queryWrapper, false);

                emailExist = user != null;
            }
        }
        if (!StringUtils.isEmpty(username)) {
            username = username.trim();
            LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(UserInfo::getUsername, username);
            UserInfo user = userInfoEntityService.getOne(queryWrapper, false);

            usernameExist = user != null;
        }
        CheckUsernameOrEmailVO checkUsernameOrEmailVO = new CheckUsernameOrEmailVO();
        checkUsernameOrEmailVO.setUsername(usernameExist);
        checkUsernameOrEmailVO.setEmail(emailExist);
        return checkUsernameOrEmailVO;
    }

    public UserHomeVO getUserHomeInfo(String uid, String username) throws StatusFailException {
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();

        if (StringUtils.isEmpty(uid) && StringUtils.isEmpty(username)) {
            if (userRole != null) {
                uid = userRole.getUid();
            } else {
                throw new StatusFailException("参数错误");
            }
        }
        UserHomeVO userHomeVO = userRecordEntityService.getUserHomeInfo(uid, username);
        if (userHomeVO == null) {
            throw new StatusFailException("用户不存在");
        }

        QueryWrapper<UserAcproblem> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uid", userHomeVO.getUid())
                .select("distinct pid", "submit_id")
                .orderByAsc("submit_id");

        List<UserAcproblem> acproblemList = userAcproblemEntityService.list(queryWrapper);
        List<Long> pidList = acproblemList.stream().map(UserAcproblem::getPid).collect(Collectors.toList());

        List<String> disPlayIdList = new LinkedList<>();
        if (!pidList.isEmpty()) {
            QueryWrapper<Problem> problemQueryWrapper = new QueryWrapper<>();
            problemQueryWrapper.select("id", "problem_id", "difficulty");
            problemQueryWrapper.in("id", pidList);

            List<Problem> problems = problemEntityService.list(problemQueryWrapper);

            Map<Integer, List<UserHomeProblemVO>> map = problems.stream()
                    .map(this::convertProblemVO)
                    .collect(Collectors.groupingBy(UserHomeProblemVO::getDifficulty));

            userHomeVO.setSolvedGroupByDifficulty(map);

            disPlayIdList = problems.stream().map(Problem::getProblemId).collect(Collectors.toList());
        }
        userHomeVO.setSolvedList(disPlayIdList);

        QueryWrapper<Session> sessionQueryWrapper = new QueryWrapper<>();
        sessionQueryWrapper.eq("uid", userHomeVO.getUid())
                .orderByDesc("gmt_create")
                .last("limit 1");

        Session recentSession = sessionEntityService.getOne(sessionQueryWrapper, false);
        if (recentSession != null) {
            userHomeVO.setRecentLoginTime(recentSession.getGmtCreate());
        }

        return userHomeVO;
    }

    private UserHomeProblemVO convertProblemVO(Problem problem) {
        return UserHomeProblemVO.builder()
                .problemId(problem.getProblemId())
                .id(problem.getId())
                .difficulty(problem.getDifficulty())
                .build();
    }

    public UserCalendarHeatmapVO getUserCalendarHeatmap(String uid, String username) throws StatusFailException {
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        if (StringUtils.isEmpty(uid) && StringUtils.isEmpty(username)) {
            if (userRole != null) {
                uid = userRole.getUid();
            } else {
                throw new StatusFailException("参数错误");
            }
        }
        UserCalendarHeatmapVO userCalendarHeatmapVO = new UserCalendarHeatmapVO();
        userCalendarHeatmapVO.setEndDate(DateUtil.format(new Date(), "yyyy-MM-dd"));

        List<Judge> lastYearUserJudgeList = userRecordEntityService.getLastYearUserJudgeList(uid, username);
        if (CollectionUtils.isEmpty(lastYearUserJudgeList)) {
            userCalendarHeatmapVO.setDataList(new ArrayList<>());
            return userCalendarHeatmapVO;
        }

        HashMap<String, Integer> tmpRecordMap = new HashMap<>();
        for (Judge judge : lastYearUserJudgeList) {
            Date submitTime = judge.getSubmitTime();
            String dateFormat = DateUtil.format(submitTime, "yyyy-MM-dd");
            tmpRecordMap.merge(dateFormat, 1, Integer::sum);
        }
        List<HashMap<String, Object>> dataList = new ArrayList<>();
        for (Map.Entry<String, Integer> record : tmpRecordMap.entrySet()) {
            HashMap<String, Object> tmp = new HashMap<>(2);
            tmp.put("date", record.getKey());
            tmp.put("count", record.getValue());
            dataList.add(tmp);
        }
        userCalendarHeatmapVO.setDataList(dataList);

        return userCalendarHeatmapVO;
    }

    public UserInfoVO changeUserInfo(UserInfoVO userInfoVo) throws StatusFailException {
        commonValidator.validateContentLength(userInfoVo.getRealname(), "真实姓名", 50);
        commonValidator.validateContentLength(userInfoVo.getNickname(), "昵称", 20);
        commonValidator.validateContentLength(userInfoVo.getSignature(), "个性简介", 65535);
        commonValidator.validateContentLength(userInfoVo.getBlog(), "博客", 255);
        commonValidator.validateContentLength(userInfoVo.getGithub(), "Github", 255);
        commonValidator.validateContentLength(userInfoVo.getSchool(), "学校", 100);
        commonValidator.validateContentLength(userInfoVo.getNumber(), "学号", 200);
        commonValidator.validateContentLength(userInfoVo.getCfUsername(), "Codeforces用户名", 255);

        // 获取当前登录的用户
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();

        LambdaUpdateWrapper<UserInfo> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(UserInfo::getUuid, userRole.getUid())
                .set(UserInfo::getCfUsername, userInfoVo.getCfUsername())
                .set(UserInfo::getRealname, userInfoVo.getRealname())
                .set(UserInfo::getNickname, userInfoVo.getNickname())
                .set(UserInfo::getSignature, userInfoVo.getSignature())
                .set(UserInfo::getBlog, userInfoVo.getBlog())
                .set(UserInfo::getGender, userInfoVo.getGender())
                .set(UserInfo::getGithub, userInfoVo.getGithub())
                .set(UserInfo::getSchool, userInfoVo.getSchool())
                .set(UserInfo::getNumber, userInfoVo.getNumber());

        boolean isOk = userInfoEntityService.update(updateWrapper);

        if (isOk) {
            UserRolesVO userRolesVO = userRoleEntityService.getUserRoles(userRole.getUid(), null);
            // 更新session
            BeanUtil.copyProperties(userRolesVO, userRole);
            UserInfoVO userInfoVO = new UserInfoVO();
            BeanUtil.copyProperties(userRolesVO, userInfoVO, "roles");
            userInfoVO.setRoleList(userRolesVO.getRoles().stream().map(Role::getRole).collect(Collectors.toList()));
            return userInfoVO;
        } else {
            throw new StatusFailException("更新个人信息失败！");
        }
    }

    public ChangeAccountVO changePassword(ChangePasswordDTO changePasswordDto, HttpServletRequest request) throws StatusFailException, StatusSystemErrorException {
        String oldPassword = changePasswordDto.getOldPassword();
        String newPassword = changePasswordDto.getNewPassword();

        if (StringUtils.isEmpty(oldPassword) || StringUtils.isEmpty(newPassword)) {
            throw new StatusFailException("参数错误");
        }
        if (newPassword.length() < 6 || newPassword.length() > 20) {
            throw new StatusFailException("新密码长度应该为6~20位！");
        }
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();

        String lockKey = Constants.Account.CODE_CHANGE_PASSWORD_LOCK.getCode() + userRole.getUid();
        String countKey = Constants.Account.CODE_CHANGE_PASSWORD_FAIL.getCode() + userRole.getUid();

        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(UserInfo::getUuid, UserInfo::getPassword, UserInfo::getEmail).eq(UserInfo::getUuid, userRole.getUid());
        UserInfo userInfo = userInfoEntityService.getOne(queryWrapper, false);

        ChangeAccountVO resp = new ChangeAccountVO();
        if (redisUtils.hasKey(lockKey)) {
            long expire = redisUtils.getExpire(lockKey);
            Date now = new Date();
            long minute = expire / 60;
            long second = expire % 60;

            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            Date afterDate = new Date(now.getTime() + expire * 1000);

            IPInfo ipInfo = IPInfoUtils.getIpInfo(IpUtils.getUserIpAddr(request));
            String address = ipInfo.getCountry() + ":" + ipInfo.getProvince() + ipInfo.getAddress();
            String content = "警告通知：" + userRole.getRealname() + "您好，" +
                    "系统监测到您的账号在地址：[" + address + "]异常的频繁密码修改！为了账号安全，若非本人操作，请尽快加强密码强度！";
            emailManager.sengCommonEmail(userInfo.getEmail(), "密码修改异常提醒", content);

            String msg = "由于您多次修改密码失败，修改密码功能已锁定，请在" + minute + "分" + second + "秒后(" + formatter.format(afterDate) + ")再进行尝试！";
            resp.setCode(403);
            resp.setMsg(msg);
            return resp;
        }

        if (userInfo.getPassword().equals(SecureUtil.md5(oldPassword))) {
            LambdaUpdateWrapper<UserInfo> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(UserInfo::getPassword, SecureUtil.md5(newPassword)).eq(UserInfo::getUuid, userRole.getUid());
            boolean updated = userInfoEntityService.update(updateWrapper);
            if (updated) {
                resp.setCode(200);
                resp.setMsg("修改密码成功！系统将于5秒钟后退出，请重新登录！");
                redisUtils.del(countKey);
                return resp;
            } else {
                throw new StatusSystemErrorException("修改密码失败！");
            }
        } else {
            Integer count = (Integer) redisUtils.get(countKey);
            if (count == null) {
                redisUtils.set(countKey, 1, 60 * 30);
                count = 0;
            } else if (count < 5) {
                redisUtils.incr(countKey, 1);
            }
            count++;
            if (count == 5) {
                redisUtils.del(countKey); // 清空统计
                redisUtils.set(lockKey, "lock", 60 * 30); // 设置锁定更改
            }
            resp.setCode(400);
            resp.setMsg("原密码错误！您已连续输入错误" + count + "次，" + (5 - count) + "次后，将锁定30分钟！");

            return resp;
        }
    }

    public void getChangeEmailCode(String email) throws StatusFailException {
        String lockKey = Constants.Email.CHANGE_EMAIL_LOCK + email;
        if (redisUtils.hasKey(lockKey)) {
            throw new StatusFailException("对不起，您的操作频率过快，请在" + redisUtils.getExpire(lockKey) + "秒后再次发送修改邮件！");
        }

        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(UserInfo::getUuid, UserInfo::getEmail).eq(UserInfo::getEmail, email);
        UserInfo emailUserInfo = userInfoEntityService.getOne(queryWrapper, false);

        if (emailUserInfo != null) {
            if (Objects.equals(emailUserInfo.getUuid(), userRole.getUid())) {
                throw new StatusFailException("新邮箱与当前邮箱一致，请不要重复设置！");
            } else {
                throw new StatusFailException("该邮箱已被他人使用，请重新设置其它邮箱！");
            }
        }

        String code = RandomUtil.randomNumbers(6);
        redisUtils.set(Constants.Email.CHANGE_EMAIL_KEY_PREFIX.getValue() + email, code, 5 * 60); //默认验证码有效10分钟
        emailManager.sendChangeEmailCode(email, userRole.getUsername(), code);
        redisUtils.set(lockKey, 0, 30);
    }

    public ChangeAccountVO changeEmail(ChangeEmailDTO changeEmailDto) throws StatusFailException, StatusSystemErrorException {
        String password = changeEmailDto.getPassword();
        String newEmail = changeEmailDto.getNewEmail();
        String code = changeEmailDto.getCode();

        if (StrUtil.hasBlank(password, newEmail, code)) {
            throw new StatusFailException("参数错误！");
        }
        if (!Validator.isEmail(newEmail)) {
            throw new StatusFailException("邮箱格式错误！");
        }

        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        String lockKey = Constants.Account.CODE_CHANGE_EMAIL_LOCK + userRole.getUid(); // 验证码锁定
        String countKey = Constants.Account.CODE_CHANGE_EMAIL_FAIL + userRole.getUid(); // 验证码错误次数

        ChangeAccountVO resp = new ChangeAccountVO();
        if (redisUtils.hasKey(lockKey)) {
            long expire = redisUtils.getExpire(lockKey);
            Date now = new Date();
            long minute = expire / 60;
            long second = expire % 60;
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            resp.setCode(403);
            Date afterDate = new Date(now.getTime() + expire * 1000);
            String msg = "由于您多次修改邮箱失败，修改邮箱功能已锁定，请在" + minute + "分" + second + "秒后(" + formatter.format(afterDate) + ")再进行尝试！";
            resp.setMsg(msg);
            return resp;
        }

        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(UserInfo::getUuid, UserInfo::getEmail).eq(UserInfo::getEmail, changeEmailDto.getNewEmail());
        UserInfo emailUserInfo = userInfoEntityService.getOne(queryWrapper, false);

        if (emailUserInfo != null) {
            if (Objects.equals(emailUserInfo.getUuid(), userRole.getUid())) {
                throw new StatusFailException("新邮箱与当前邮箱一致，请不要重复设置！");
            } else {
                throw new StatusFailException("该邮箱已被他人使用，请重新设置其它邮箱！");
            }
        }

        LambdaQueryWrapper<UserInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(UserInfo::getUuid, UserInfo::getPassword).eq(UserInfo::getUuid, userRole.getUid());
        UserInfo userInfo = userInfoEntityService.getOne(wrapper);

        String cacheCodeKey = Constants.Email.CHANGE_EMAIL_KEY_PREFIX.getValue() + newEmail;
        String cacheCode = (String) redisUtils.get(cacheCodeKey);
        if (cacheCode == null) {
            throw new StatusFailException("修改邮箱验证码不存在或已过期，请重新发送！");
        }

        if (!Objects.equals(cacheCode, code)) {
            Integer count = (Integer) redisUtils.get(countKey);
            if (count == null) {
                redisUtils.set(countKey, 1, 60 * 30); // 三十分钟不尝试，该限制会自动清空消失
                count = 0;
            } else if (count < 5) {
                redisUtils.incr(countKey, 1);
            }
            count++;
            if (count == 5) {
                redisUtils.del(countKey); // 清空统计
                redisUtils.set(lockKey, "lock", 60 * 30); // 设置锁定更改
            }
            resp.setCode(400);
            resp.setMsg("验证码错误！您已连续输入错误" + count + "次，" + (5 - count) + "次后，将锁定30分钟！");
            return resp;
        }

        if (userInfo.getPassword().equals(SecureUtil.md5(password))) {
            LambdaUpdateWrapper<UserInfo> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(UserInfo::getEmail, newEmail).eq(UserInfo::getUuid, userRole.getUid());

            boolean updated = userInfoEntityService.update(updateWrapper);
            if (updated) {
                UserRolesVO userRolesVO = userRoleEntityService.getUserRoles(userRole.getUid(), null);
                UserInfoVO userInfoVo = new UserInfoVO();

                BeanUtil.copyProperties(userRolesVO, userInfoVo, "roles");
                userInfoVo.setRoleList(userRolesVO
                        .getRoles()
                        .stream()
                        .map(Role::getRole)
                        .collect(Collectors.toList()));
                resp.setCode(200);
                resp.setMsg("修改邮箱成功！");
                resp.setUserInfo(userInfoVo);
                // 清空记录
                redisUtils.del(countKey, cacheCodeKey);
                return resp;
            } else {
                throw new StatusSystemErrorException("修改邮箱失败！");
            }
        } else {
            Integer count = (Integer) redisUtils.get(countKey);
            if (count == null) {
                redisUtils.set(countKey, 1, 60 * 30); // 三十分钟不尝试，该限制会自动清空消失
                count = 0;
            } else if (count < 5) {
                redisUtils.incr(countKey, 1);
            }
            count++;
            if (count == 5) {
                redisUtils.del(countKey); // 清空统计
                redisUtils.set(lockKey, "lock", 60 * 30); // 设置锁定更改
            }

            resp.setCode(400);
            resp.setMsg("密码错误！您已连续输入错误" + count + "次，" + (5 - count) + "次后，将锁定30分钟！");
            return resp;
        }
    }
}
